package net.databinder.components.hib;
import net.databinder.components.AjaxCell;
import net.databinder.components.AjaxOnKeyPausedUpdater;
import net.databinder.models.hib.CriteriaBuilder;
import net.databinder.models.hib.PropertyQueryBinder;
import net.databinder.models.hib.QueryBinder;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.ResourceReference;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.image.Image;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.hibernate.Criteria;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
/**
* Panel for a "live" search field with a clear button. Instances of this class must
* implement the onUpdate method to register external components for updating.
* It is possible to override the search button text with the key "searchbutton.text"
* The SearchPanel model maps to the text of the search.
* @author Nathan Hamblen
*/
public abstract class SearchPanel extends Panel {
private TextField search;
/**
* @param id Wicket id
*/
public SearchPanel(final String id) {
super(id, new Model());
add(new SearchForm("searchForm"));
}
/** Use the given model (must not be read-only ) for the search string */
public SearchPanel(final String id, final IModel searchModel) {
super(id, searchModel);
add(new SearchForm("searchForm"));
}
@Override
/** Sets model to search component. */
public MarkupContainer setDefaultModel(final IModel model) {
return search.setDefaultModel(model);
}
/**
* Override to add components to be updated (or JavaScript to be executed)
* when the search string changes. Remember that added components must
* have a markup id; use component.setMarkupId(true) to assign one
* programmatically.
* @param target Ajax target to register components for update
*/
public abstract void onUpdate(AjaxRequestTarget target);
/**
* Binds the search model to a "search" parameter in a query. The value in the
* search field will be bracketed by percent signs (%) for a find-anywhere match.
* In the query itself, "search" must be the name of the one and only parameter
* If your needs differ, bind the model passed in to the SearchPanel constructor
* to your own IQueryBinder instance; this is a convenience method.
* @return binder for a "search" parameter
*/
public QueryBinder getQueryBinder() {
return new PropertyQueryBinder(this);
}
/**
* Adds a criterion that will match the current search string anywhere within
* any of the given properties. If the search is empty, no criterion is added.
* @param searchProperty one or more properties to be searched
* @return builder to be used with list model or data provider
*/
public CriteriaBuilder getCriteriaBuilder(final String... searchProperty) {
return getCriteriaBuilder(MatchMode.ANYWHERE, searchProperty);
}
/**
* Adds a criterion that will match the current search string within (depending on the MatchMode)
* any of the given properties. If the search is empty, no criterion is added.
* @param matchMode used against all properties
* @param searchProperty one or more properties to be searched
* @return builder to be used with list model or data provider
*/
public CriteriaBuilder getCriteriaBuilder(final MatchMode matchMode, final String... searchProperty) {
return new CriteriaBuilder() {
public void build(final Criteria criteria) {
final String search = (String) getDefaultModelObject();
if (search != null) {
final Disjunction d = Restrictions.disjunction();
for (final String prop : searchProperty) {
d.add(Property.forName(prop).like(search, matchMode));
}
criteria.add(d);
}
}
};
}
/** @return search string bracketed by the % wildcard */
public String getSearch() {
return getDefaultModelObject() == null ? null : "%" + getDefaultModelObject() + "%";
}
/** Resets the search model, The default behavior is setting it to null,
* but it is possible to override with your own custom defaults. */
public void resetSearchModelObject() {
setDefaultModelObject(null);
}
/** Form with AJAX components and their AjaxCells. */
public class SearchForm extends Form {
@SuppressWarnings("unchecked")
public SearchForm(final String id) {
super(id);
final AjaxCell searchWrap = new AjaxCell("searchWrap");
add(searchWrap);
search = new TextField("searchInput", SearchPanel.this.getDefaultModel());
search.setOutputMarkupId(true);
searchWrap.add(search);
final AjaxCell clearWrap = new AjaxCell("clearWrap");
add(clearWrap);
final AjaxLink clearLink = new AjaxLink("clearLink") {
/** Clear field and register updates. */
@Override
public void onClick(final AjaxRequestTarget target) {
resetSearchModelObject();
target.addComponent(searchWrap);
target.addComponent(clearWrap);
onUpdate(target);
}
/** Hide when search is blank. */
@Override
public boolean isVisible() {
return SearchPanel.this.getDefaultModelObject() != null;
}
};
clearLink.setOutputMarkupId(true);
clearLink.add( new Image("clear",
new ResourceReference(this.getClass(), "clear.png")));
clearWrap.add(clearLink);
// triggered when user pauses or tabs out
search.add(new AjaxOnKeyPausedUpdater() {
@Override
protected void onUpdate(final AjaxRequestTarget target) {
target.addComponent(clearWrap);
SearchPanel.this.onUpdate(target);
}
});
}
}
}